using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using T.FAS.Runtime.Base.Entity;
using T.FAS.Runtime.DataAccess.Common;
using T.FAS.Runtime.DataAccess.SPI;
using T.FAS.Runtime.Metadata.Entity;

namespace T.FAS.Runtime.DataAccess.Engine
{
    /// <summary>
    /// 统一数据访问服务插件管理类，插件执行器不可以单例化，每个插件类都会实例化执行，需要有独立的上下文
    /// </summary>
    public class DataAccessPluginExecutor
    {
        #region Fields & Properties
        private static object s_lockObj = new object();
        private static readonly IDACLogService s_localLogService = DACLogFactory.GetDACLogService();
        /// <summary>
        /// 插件集合
        /// </summary>
        private List<IDataAccessPlugin> _dataAccessPlugins;
        public List<IDataAccessPlugin> DataAccessPlugins
        {
            get
            {
                return _dataAccessPlugins;
            }
        }

        /// <summary>
        /// 控制特殊注入点的有关联性插件的执行顺序，比如状态机跟ChangeLog
        /// </summary>
        private static Dictionary<InjectionPoint, List<string>> PluginSchedule
            = new Dictionary<InjectionPoint, List<string>>
            {
                {
                    InjectionPoint.BeforeExecuteSQL, new List<string>
                    {
                        PluginConst.ChangeLog.Code,
                        PluginConst.StateMachine.Code
                    }
                },
                { 
                    InjectionPoint.Finish, new List<string>
                    {
                        PluginConst.ChangeLog.Code,
                        PluginConst.StateMachine.Code
                    }
                }
            };

        #endregion
        
        /// <summary>
        /// 构造函数，插件执行器不可以单例化，每个插件类都会实例化执行，需要有独立的上下文
        /// </summary>
        public DataAccessPluginExecutor() 
        {
            _dataAccessPlugins = new List<IDataAccessPlugin>();
            LoadPlugIn();
        }

        #region 插件执行重载
        /// <summary>
        /// 初始化所有插件执行的上下文
        /// </summary>
        /// <param name="invokeContext"></param>
        public void BuildContext(InvokeContext invokeContext)
        {
            List<ActionResult> actionResults = new List<ActionResult>();
            var plugins = GetCorrectPlugIn();
            BuildContext(invokeContext, actionResults, plugins);
        }

        /// <summary>
        /// 执行所有插件逻辑
        /// </summary>
        /// <param name="MethodType"></param>
        /// <param name="Point"></param>
        /// <param name="extExecuteData"></param>
        /// <returns></returns>
        public List<ActionResult> Execute(InjectionMethodType MethodType, InjectionPoint Point, DataAccessPluginExtParam extExecuteData = null)
        {
            var plugins = GetCorrectPlugIn();
            return ExecutePlugins(MethodType, Point, plugins, extExecuteData);
        }

        public List<ActionResult> Execute(string SpecifyPluginName, InjectionMethodType MethodType, InjectionPoint Point, DataAccessPluginExtParam extExecuteData = null)
        {
            var SpecifyPlugins = _dataAccessPlugins.Where(plug => plug.PluginCode == SpecifyPluginName).ToList();
            return ExecutePlugins(MethodType, Point, SpecifyPlugins, extExecuteData);
        }

        public List<ActionResult> Execute(List<IDataAccessPlugin> SpecifyPlugins, InjectionMethodType MethodType, InjectionPoint Point, DataAccessPluginExtParam extExecuteData = null)
        {
            return ExecutePlugins(MethodType, Point, SpecifyPlugins, extExecuteData);
        }

        public List<ActionResult> Execute(IDataAccessPlugin SpecifyPlugin, InjectionMethodType MethodType, InjectionPoint Point, DataAccessPluginExtParam extExecuteData = null)
        {
            return ExecutePlugins(MethodType, Point, new List<IDataAccessPlugin> { SpecifyPlugin }, extExecuteData);
        } 

        #endregion

        /// <summary>
        /// 根据插件code获取插件实例
        /// </summary>
        /// <param name="pluginCode"></param>
        /// <returns></returns>
        public IDataAccessPlugin GetSpecifyPlug(string pluginCode)
        {
            var SpecifyPlugin = _dataAccessPlugins.FirstOrDefault(plug => plug.PluginCode == pluginCode);
            return SpecifyPlugin;
        }

        /// <summary>
        /// 提供Build InvokeContext方法
        /// </summary>
        /// <param name="sourceMeta"></param>
        /// <param name="nodeID"></param>
        /// <param name="sourceData"></param>
        /// <param name="sourceDataID"></param>
        /// <param name="sourceDataIDs"></param>
        /// <param name="extProperties"></param>
        /// <returns></returns>
        public static InvokeContext BuildInvokeContext(string metaType,
                                                IMetaDataContent sourceMeta,
                                                string nodeID = null, 
                                                TRecord sourceData = null, 
                                                string sourceDataID = null, 
                                                List<string> sourceDataIDs = null, 
                                                Dictionary<string, object> extProperties = null)
        {
            return new InvokeContext {
                MetaType = metaType,
                SourceMeta = sourceMeta,
                NodeID = nodeID,
                SourceData = sourceData,
                SourceDataID = sourceDataID,
                SourceDataIDs = sourceDataIDs,
                ExtProperties = extProperties,
            };

        }

        /// <summary>
        /// 处理action result
        /// </summary>
        /// <param name="actionResult"></param>
        public static void DefaultHandleActionResult(List<ActionResult> actionResult)
        {
            var errorResult = actionResult.Where(r => !r.Result).ToList();
            var errorMsgArr = errorResult.Select(r => r.Message).ToList();
            var errorMsgs = string.Join(ExceptionManage.LineFeedSymbol, errorMsgArr);
            var needThrowErrorResult = errorResult.Where(e => e.ActionErrorHandleType == ActionErrorHandleType.Throw).ToList();
            foreach (var item in needThrowErrorResult)
            {
                if (item.Exception != null)
                    throw new FasException(DataAccessErrorCode.PlugIn.Error, item.Message, item.Exception);
                else
                    throw new FasException(DataAccessErrorCode.PlugIn.Error, item.Message);
            }
        }

        #region Private Funcs

        /// <summary>
        /// 初始化插件
        /// </summary>
        private void LoadPlugIn()
        {
            //todo:根据配置动态注册插件
            _dataAccessPlugins.Add(new StateMachinePlugin());
            _dataAccessPlugins.Add(new AuthorityPlugin());
            _dataAccessPlugins.Add(new ChangeLogPlugin());
        }

        /// <summary>
        /// 获取满足当前条件点执行的插件
        /// </summary>
        /// <param name="metaType"></param>
        /// <returns></returns>
        private List<IDataAccessPlugin> GetCorrectPlugIn(string metaType = null)
        {
            List<IDataAccessPlugin> result = new List<IDataAccessPlugin>();
            if (_dataAccessPlugins != null && _dataAccessPlugins.Count > 0)
            {
                foreach (var plugin in _dataAccessPlugins)
                {
                    if(string.IsNullOrEmpty(metaType) ||
                        plugin.MetaType.Contains(metaType))
                    {
                        result.Add(plugin);
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// 获取满足当前条件点执行的插件
        /// </summary>
        /// <param name="specifyPlugins"></param>
        /// <param name="metaType"></param>
        /// <param name="methodType"></param>
        /// <param name="point"></param>
        /// <returns></returns>
        private List<IDataAccessPlugin> GetCorrectPlugIn(List<IDataAccessPlugin> specifyPlugins , string metaType, InjectionMethodType methodType, InjectionPoint point)
        {
            List<IDataAccessPlugin> result = new List<IDataAccessPlugin>();
            if (specifyPlugins != null && specifyPlugins.Count > 0)
            {
                foreach (var plugin in specifyPlugins)
                {
                    if (plugin.MetaType.Contains(metaType)
                        && (plugin.InjectionMethodType & (int)methodType) == (int)methodType
                        && (plugin.InjectionPoint & (int)point) == (int)point)
                    {
                        result.Add(plugin);
                    }
                }
            }
            return result;
        }

        private static List<ActionResult> ExecutePlugins(InjectionMethodType methodType, InjectionPoint point, List<IDataAccessPlugin> plugins, DataAccessPluginExtParam extExecuteData)
        {
            if (plugins == null)
                return null;
            var schedulePlugins = ReschedulePlugins(point, plugins);
            List<ActionResult> actionResults = new List<ActionResult>();
            foreach (var plugin in schedulePlugins)
            {
                try
                {
                    var executeReulst = plugin.Execute(methodType, point, extExecuteData);
                    actionResults.Add(executeReulst);
                }
                catch (Exception e)
                {
                    s_localLogService.WriteError(e, $"统一数据访问服务{plugin.PluginCode}插件执行异常,Msg:{e.Message}");
                }
            }
            return actionResults;
        }

        private static List<IDataAccessPlugin> ReschedulePlugins(InjectionPoint point, List<IDataAccessPlugin> plugins)
        {
            if (PluginSchedule.ContainsKey(point))
            {
                var schedule = PluginSchedule[point];
                List<IDataAccessPlugin> result = new List<IDataAccessPlugin>();
                foreach (var plugin in plugins)
                {
                    if (!schedule.Contains(plugin.PluginCode))
                    {
                        result.Add(plugin);
                    }
                }
                //特殊编排的插件默认放到最后执行
                foreach (var item in schedule)
                {
                    var instance = plugins.FirstOrDefault(p => p.PluginCode == item);
                    if (instance != null)
                        result.Add(instance);
                }
                return result;
            }
            return plugins;
        }

        private static void BuildContext(InvokeContext invokeContext, List<ActionResult> actionResults, List<IDataAccessPlugin> plugins)
        {
            foreach (var plugin in plugins)
            {
                plugin.BuildInvokeContext(invokeContext);
            }
        }
        #endregion
    }
}
